#include <cstdio>
#include <cstdlib>

using namespace std;

#include "Memory.h"
#include "CPU.h"

#define DBG_MSG(msg) 
//cerr << (msg) << endl

CPU::CPU(int setch):IR(-1), mem(setch),
					ZERO(0),ONE(1),funcRetAddr()
{
	curInstr = "No instruction fetched";
	binInstr = "0";
	str = NULL;
}

CPU::~CPU ()
{
}

string CPU::currentOperation ()
{
	return curInstr;
}

string CPU::binaryOperation ()
{
	return binInstr;
}

int CPU::currentOperationNum ()
{
	return opNum;
}

int CPU::getCurrentInstructionNumber ()
{
	return IR;
}

int CPU::getCurrentOperationCount ()
{
	return opCount;
}

void CPU::Load (char* szFileName)
{
	char buf[1024];
	ifstream ifile (szFileName);
	if (!ifile) {
		errprint ("Unable to open file");
	}

	ifile >> instrTotal >> IR;
	ifile.get (); // to remove new line
	IR--;

	for (int i = 0; i < instrTotal ; i++)
	{
		ifile.getline (buf, 1024);
		asm_instrs.push_back (buf);
		ifile.getline (buf, 1024);
		mem.Text.push_back (buf);
	}
	
	instrCount = opCount = opNum = 0;
	opTotal = -1;
}

int CPU::Fetch ()
{
	DBG_MSG("Fetch Entered");
	opNum = 0;opTotal = 2;
	// Fetches the instruction pointed to by IR
	if (IR < static_cast<int>(mem.Text.size()))
		curInstr = "Fetching  ";
	else 
		return -1;

	opCount++;opNum++;
	
	DBG_MSG("Fetch Leaving");
	return 0;
}

void CPU::Decode ()
{
	DBG_MSG("Decode Entered");
	if (str != NULL) delete str;
	str = new stringstream (mem.Text[IR]);
	opTotal = 0;
	(*str) >> opTotal;

	curInstr = asm_instrs[IR];
	
	opCount++;opNum++;IR++;
	opTotal += 2; // to account for fetch and decode
	DBG_MSG("Decode Leaving");
}

int CPU::ExecuteNextCycle ()
{
	DBG_MSG("ExecuteNextCycle Entered");
	if (opNum < opTotal) {
		// There are more instructions, so no need to fetch
		MicroProcessor (*str);
		opCount++;opNum++;
		DBG_MSG("ExecuteNextCycle Leaving");
		return 0;
	}
	DBG_MSG("ExecuteNextCycle Leaving");
	return -1;
}

int CPU::ExecuteNextInstruction ()
{
	DBG_MSG("ExecuteNextInstruction Entered");
	if (opNum >= opTotal) return -1;

	while (opNum < opTotal) {
		MicroProcessor (*str);
		opCount++;opNum++;
	}
	DBG_MSG("ExecuteNextInstruction Leaving");
	return 0;
}

void CPU::MicroProcessor (stringstream& str)
{
	int *latch = NULL;
	const int *out = NULL;
	INSTR instr = 0;
	str >> instr;

	char buf[20];
	sprintf (buf, "0x%08x", instr);
	binInstr = buf;
	
	// Check if it is an ALU operation
	if (instr & ALU_ADD) {
		ACC = A + B;
		return;
	} else if (instr & ALU_AND) {
		ACC = A & B;
		return;
	} else if (instr & ALU_OR) {
		ACC = A | B;
		return;
	} else if ((instr & ALU_MUL) && (instr & MAR_IMMED)) {
		if (B == 0)
			errprint ("Divide by zero exception.");
		ACC = A / B;
		return;
	} else if (instr & ALU_MUL) {
		ACC = A * B;
		return;
	} else if (instr & ALU_NOT) {
		ACC = ~ACC;
		return;
	} else if ((instr & WRITE_READ) || (instr & DATA_ADDR)) {
		// Checking if it is a select mem operation
		if ((instr & WRITE_READ) && (!(instr & DATA_ADDR)) 
				&& (instr & MAR_OUT)) {
			if (instr & MAR_IMMED)
				return;
			else {
				str >> MAR;
				mem.SelectMemLocation (MAR);
			}
		} // Checking if it is a mem read operation
		else if ((!(instr & WRITE_READ)) && (instr & DATA_ADDR)
				&& (instr & MDR_LATCH)) {
			if (instr & MAR_IMMED) {
				str >> MDR;
			}
			else {
				int temp;
				str >> temp;
				MDR = mem.ReadFromStack (temp);
			}
		} // Checking if it is a mem write operation
		else if ((instr & WRITE_READ) && (instr & DATA_ADDR)
				&& (instr & MDR_OUT)) {
			int temp;
			str >> temp;
			mem.WriteToStack (temp, MDR);
		}
		else {
			errprint ("Illegal instruction ");
		}
		return;
	}
	// it is a jump instruction
	else if (instr & BCOND_BL) {
		if (ACC < 0)
			str >> IR;
		return;
	} 
	else if (instr & BCOND_BLE) {
		if (ACC <= 0)
			str >> IR;
		return;
	}
	else if (instr & BCOND_BG) {
		if (ACC > 0)
			str >> IR;
		return;
	}
	else if (instr & BCOND_BGE) {
		if( ACC >= 0)
			str >> IR;
		return;
	}
	// Not equal
	else if (instr & BCOND_BE && instr && MAR_IMMED) {
		if (ACC != 0)
			str >> IR;
		return;
	}
	else if (instr & BCOND_BE) {
		if (ACC == 0)
			str >> IR;
		return;
	}
	else if (instr & BCOND_BA) {
		str >> IR;
		return;
	}
	else if (instr & BCOND_JMP) {
		funcRetAddr.push (IR);
		str >> IR;
		return;
	}
	else if (instr & BCOND_RET) {
		IR = funcRetAddr.top ();
		funcRetAddr.pop ();
		return;
	}
	// nop instruction
	else if (instr == 0) {
		return;
	}
	// No it is not an operation, so proceed to copy a value from one
	// register to another
	
	// find the out and latch registers
       	else {
		if (instr & ACC_OUT) out = &ACC;
		if (instr & ACC_LATCH) latch = &ACC;
		if ((instr & A_LATCH) && (latch == NULL)) latch = &A;
		if ((instr & B_LATCH) && (latch == NULL)) latch = &B;
		if ((instr & ONE_OUT) && (out == NULL)) out = &ONE;
		if ((instr & ZERO_OUT) && (out == NULL)) out = &ZERO;
		if ((instr & PC_OUT) && (out == NULL)) out = &PC;
		if ((instr & PC_LATCH) && (latch == NULL)) latch = &PC;
		if ((instr & MAR_OUT) && (out == NULL)) out = &MAR;
		if ((instr & MAR_LATCH) && (latch == NULL)) latch = &MAR;
		if ((instr & MDR_OUT) && (out == NULL)) out = &MDR;
		if ((instr & MDR_LATCH) && (latch == NULL)) latch = &MDR;
		if ((instr & TEMP_OUT) && (out == NULL)) out = &TEMP;
		if ((instr & TEMP_LATCH) && (latch == NULL)) latch = &TEMP;
		if ((instr & IR_LATCH) && (latch == NULL))	latch = &IR;
	
		if (latch != NULL && out != NULL) {
			*latch = *out;
		} else {
			errprint ("Illegal microinstruction");
		}
	}

}

void CPU::errprint (const char *msg)
{
	string mstr = msg;
	mstr = mstr + " " + curInstr;
	errprintline (mstr.c_str(), IR);
}
